/*
 * JF Updater: Auto-updater for modified Android OS
 *
 * Copyright (c) 2009 Sergi Vélez
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package jfupdater.ui;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URI;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jfupdater.service.PlainTextUpdateServer;
import jfupdater.service.UpdateInfo;
import jfupdater.utils.Preferences;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

public class UpdateProcessInfo extends IUpdateProcessInfo {

	private static final String TAG = "UpdateProcessInfo";
	private static final String STATE_DIR = "state";
	private static final String STORED_STATE_FILENAME = "UpdateProcessInfo.ser";
	
    private static final int MENU_ID_UPDATE_NOW = 1;
    //private static final int MENU_ID_APPLY_NOW= 2;
    private static final int MENU_ID_CONFIG= 3;
    
	private static final String KEY_AVAILABLE_UPDATES = "jfupdater.availableUpdates";
	private static final String KEY_DOWNLOADING_UPDATE = "jfupdater.downloadingUpdate";
	
	public static final int REQUEST_NEW_UPDATE_LIST = 1;
	public static final int REQUEST_UPDATE_CHECK_ERROR = 2;
	
	public static final String KEY_REQUEST = "jfupdater.keyRequest";
	public static final String KEY_UPDATE_LIST = "jfupdater.updateList";
	
	
	private RadioGroup mUpdatesRG;
	private PlainTextUpdateServer mUpdateServer;
	private ProgressBar mProgressBar;
	private TextView mDownloadedBytesTextView;
	private List<UpdateInfo> mAvailableUpdates;
	private UpdateInfo mDownloadingUpdate;
	//private CheckForUpdatesTask mCheckForUpdatesTask;
	private DownloadUpdateTask mDownloadUpdateTask;
	private File mDestinationFile;
	
	private File mStoredStateFile;
	//private UpdateInfo mUpdateReady;
	
	private final class SelectUpdateButtonListener implements View.OnClickListener {
		@Override
		public void onClick(View v) {
			RadioButton checkedRB = (RadioButton) findViewById(mUpdatesRG.getCheckedRadioButtonId());
			if(checkedRB == null) return;
			
			UpdateInfo ui = (UpdateInfo) checkedRB.getTag();
			downloadRequestedUpdate(ui);
			//UpdateProcessInfo.this.finish();
		}
	}
	
	private final class ApplyUpdateButtonListener implements View.OnClickListener {
		
		private UpdateInfo mDownloadedUpdate;
		
		public ApplyUpdateButtonListener(UpdateInfo downloadedUpdate) {
			mDownloadedUpdate = downloadedUpdate;
		}

		@Override
		public void onClick(View v) {
			Intent i = new Intent(UpdateProcessInfo.this, ApplyUploadActivity.class)
						.putExtra(ApplyUploadActivity.KEY_UPDATE_INFO, mDownloadedUpdate);
			startActivity(i);
			//finish();
		}
	}
	
	private final class CancelDownloadListener implements View.OnClickListener {

		@Override
		public void onClick(View arg0) {

			new AlertDialog.Builder(UpdateProcessInfo.this)
				.setMessage(R.string.confirm_download_cancelation_dialog_message)
				.setPositiveButton(R.string.confirm_download_cancelation_dialog_yes, new DialogInterface.OnClickListener(){
					@Override
					public void onClick(DialogInterface dialog, int which) {
						if(!mDownloadUpdateTask.cancel(true)) {
							Log.w(TAG, "Unable to cancel the download");
						}
					}
				})
				.setNegativeButton(R.string.confirm_download_cancelation_dialog_no, new DialogInterface.OnClickListener(){
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.dismiss();
					}
				})
				.show();
		}
		
	};
	
    @SuppressWarnings("unchecked")
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        Preferences prefs = Preferences.getPreferences(this);
        if(prefs.isFirstRun()) {
        	prefs.configureModString();
        	prefs.setFirstRun(false);
        }
        
        restoreSavedInstanceValues(savedInstanceState);
        
        mStoredStateFile = new File(getDir(STATE_DIR, Context.MODE_PRIVATE), STORED_STATE_FILENAME);
        
        int req = getIntent().getIntExtra(KEY_REQUEST, -1);
        switch(req) {
        	case REQUEST_NEW_UPDATE_LIST:
        		mAvailableUpdates = (List<UpdateInfo>) getIntent().getSerializableExtra(KEY_UPDATE_LIST);
        		break;
        	case REQUEST_UPDATE_CHECK_ERROR:
        		//TODO
        		Log.w(TAG, "Update check error");
        		break;
        }
        
        URI uri = URI.create(getResources().getString(R.string.conf_update_server_url));
        mUpdateServer = new PlainTextUpdateServer(uri, this);
        
        String destFileName = getResources().getString(R.string.conf_update_file_name);
		mDestinationFile = new File(Environment.getExternalStorageDirectory(), destFileName);
        
    }
    
    /* (non-Javadoc)
	 * @see android.app.Activity#onStart()
	 */
	@Override
	protected void onStart() {
		super.onStart();
		
		try {
			loadState();
		} catch (FileNotFoundException e) {
			//Ignored, data was not saved
		} catch (IOException e) {
			Log.w(TAG, "Unable to restore activity status", e);
		}

		DownloadUpdateTask downloadUpdateTask = DownloadUpdateTask.INSTANCE;
        if(downloadUpdateTask != null && downloadUpdateTask.getStatus() == DownloadUpdateTask.Status.RUNNING) {
        	switchToDownloadingLayout(mDownloadingUpdate);
        	downloadUpdateTask.setIUpdateProcessInfo(this);
        } else if (mAvailableUpdates != null) {
        	switchToUpdateChooserLayout(mAvailableUpdates);
        } else {
        	switchToNoUpdatesAvailable();
        }
	}

	/* (non-Javadoc)
	 * @see android.app.Activity#onStop()
	 */
	@Override
	protected void onStop() {
		super.onStop();
		
		try {
			saveState();
		} catch (IOException e) {
			Log.w(TAG, "Unable to save state", e);
		}
	}

	private void saveState() throws IOException {
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(mStoredStateFile));
		try {
			Map<String,Serializable> data = new HashMap<String, Serializable>();
			data.put("mAvailableUpdates", (Serializable)mAvailableUpdates);
			data.put("mDownloadingUpdate", mDownloadingUpdate);
			oos.writeObject(data);
			oos.flush();
		} finally {
			oos.close();
		}
	}

	@SuppressWarnings("unchecked")
	private void loadState() throws IOException {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(mStoredStateFile));
		try {
			Map<String,Serializable> data = (Map<String, Serializable>) ois.readObject();
			
			Object o = data.get("mAvailableUpdates"); 
			if(o != null) mAvailableUpdates = (List<UpdateInfo>) o;
			
			o = data.get("mDownloadingUpdate"); 
			if(o != null) mDownloadingUpdate =  (UpdateInfo) o;
		} catch (ClassNotFoundException e) {
			Log.e(TAG, "Unable to load stored class", e);
		} finally {
			ois.close();
		}
	}

	@SuppressWarnings("unchecked")
	private void restoreSavedInstanceValues(Bundle b) {
		if(b == null) return;
		mAvailableUpdates = (List<UpdateInfo>) b.getSerializable(KEY_AVAILABLE_UPDATES);
		mDownloadingUpdate = (UpdateInfo) b.getSerializable(KEY_DOWNLOADING_UPDATE);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
    	outState.putSerializable(KEY_AVAILABLE_UPDATES, (Serializable)mAvailableUpdates);
    	outState.putSerializable(KEY_DOWNLOADING_UPDATE, mDownloadingUpdate);
	}
    
    /* (non-Javadoc)
	 * @see android.app.Activity#onNewIntent(android.content.Intent)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		
		int req = intent.getIntExtra(KEY_REQUEST, -1);
        switch(req) {
        	case REQUEST_NEW_UPDATE_LIST:
        		switchToUpdateChooserLayout((List<UpdateInfo>) intent.getSerializableExtra(KEY_UPDATE_LIST));
        		break;
        	case REQUEST_UPDATE_CHECK_ERROR:
        		//TODO
        		Log.w(TAG, "Update check error");
        		break;
        }
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        menu.add(Menu.NONE, MENU_ID_UPDATE_NOW, Menu.NONE, R.string.menu_check_now);
        menu.add(Menu.NONE, MENU_ID_CONFIG, Menu.NONE, R.string.menu_config);
        return true;
	}

	/* (non-Javadoc)
	 * @see android.app.Activity#onMenuItemSelected(int, android.view.MenuItem)
	 */
	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		switch(item.getItemId()) {
		    case MENU_ID_UPDATE_NOW:
		        checkForUpdates();
		        return true;
		    case MENU_ID_CONFIG:
		    	showConfigActivity();
		    	return true;
	        default:
	        	Log.w(TAG, "Unknown Menu ID:" + item.getItemId());
	        	break;
	    }
		   
	    return super.onMenuItemSelected(featureId, item);
	}

	private void showConfigActivity() {
		Intent i = new Intent(this, ConfigActivity.class);
		startActivity(i);
	}

	private void checkForUpdates() {
		//(mCheckForUpdatesTask = new CheckForUpdatesTask(mUpdateServer, this, true)).execute();
		new CheckForUpdatesTask(mUpdateServer, this).execute();
		Toast.makeText(this, R.string.checking_for_updates, Toast.LENGTH_SHORT).show();
		finish();
	}
	
	private void downloadRequestedUpdate(UpdateInfo ui) {
		switchToDownloadingLayout(ui);
		(mDownloadUpdateTask = new DownloadUpdateTask(this)).execute(ui);
		Toast.makeText(this, R.string.downloading_update, Toast.LENGTH_SHORT).show();
	}

	@Override
	void switchToNoUpdatesAvailable() {
		setContentView(R.layout.no_updates);
		
		((Button)findViewById(R.id.check_now_button)).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				checkForUpdates();
			}
		});
	}

	@Override
	void switchToDownloadingLayout(UpdateInfo downloadingUpdate) {
		mDownloadingUpdate = downloadingUpdate;
		setContentView(R.layout.update_download_info);
		mProgressBar = (ProgressBar) findViewById(R.id.download_progress_bar);
		mDownloadedBytesTextView = (TextView) findViewById(R.id.bytes_downloaded_text_view);
		((Button)findViewById(R.id.cancel_download_buton)).setOnClickListener(new CancelDownloadListener());
	}/*

	@Override
	void switchToUpdateChooserLayout(UpdateInfo downloadedUpdate) {
		mUpdateReady = downloadedUpdate;
		switchToUpdateChooserLayout((List<UpdateInfo>)null);
	}*/

	@Override
	void switchToUpdateChooserLayout(List<UpdateInfo> availableUpdates) {
		/*
		 * If availableUpdates is null, use the cached value.
		 * If not, cache the value for future uses
		 */
		if(availableUpdates == null) {
			availableUpdates = mAvailableUpdates;
		} else {
			mAvailableUpdates = availableUpdates;
		}
		
		setContentView(R.layout.update_chooser);
		((NotificationManager)getSystemService(NOTIFICATION_SERVICE)).cancel(R.string.not_new_updates_found_title);
		

		RadioGroup rg = mUpdatesRG = (RadioGroup) findViewById(R.id.available_updates_list);
		
		for(UpdateInfo u: availableUpdates) {
			RadioButton rb = new RadioButton(this);
			rb.setTag(u);
			rb.setText(u.displayName);
			rg.addView(rb);
		}
		
		Button selectUploadButton = (Button) findViewById(R.id.download_update_button);
		selectUploadButton.setOnClickListener(new SelectUpdateButtonListener());
		
		TextView downloadedUpdateText = (TextView) findViewById(R.id.downloaded_update_found);
		Button applyUpdateButton = (Button) findViewById(R.id.apply_update_button);
		
		UpdateInfo updateReady = checkForAReadyToApplyUpdate(availableUpdates);
		//UpdateInfo updateReady = mUpdateReady;
		
		if(updateReady != null) {
			applyUpdateButton.setOnClickListener(new ApplyUpdateButtonListener(updateReady));
			String template = getResources().getString(R.string.downloaded_update_found_text);
			downloadedUpdateText.setText(MessageFormat.format(template, updateReady.displayName, updateReady.displayVersion));
		} else {
			applyUpdateButton.setVisibility(View.GONE);
			downloadedUpdateText.setVisibility(View.GONE);
		}
	}

	/*
	 * Checking the MD5 of a large file (40MB) takes forever (> 6 minutes)
	 * Disabled for now, untill we found an alternative.
	 */
	private UpdateInfo checkForAReadyToApplyUpdate(List<UpdateInfo> availableUpdates) {
		
		File destFile = mDestinationFile;
		if(!destFile.exists()) return null;
		
		Log.i(TAG, "Found and update.zip file. Checking it's MD5");
		try {
			String calculatedMD5 = readMD5(destFile);
			Log.i(TAG, "update.zip MD5:" + calculatedMD5);
			for(UpdateInfo ui : availableUpdates) {
				if(ui.md5.equalsIgnoreCase(calculatedMD5)) return ui;
			}
		} catch (IOException e) {
			Log.w(TAG, "IOEx while checking MD5 of the update file", e);
		}
		/*
		File destFile = mDestinationFile;
		if(!destFile.exists()) return null;
		
		Log.i(TAG, "Found and update.zip file. Checking it's MD5");
		try {
			String calculatedMD5 = IOUtils.getMD5(destFile);
			Log.i(TAG, "update.zip MD5:" + calculatedMD5);
			for(UpdateInfo ui : availableUpdates) {
				if(ui.md5.equalsIgnoreCase(calculatedMD5)) return ui;
			}
		} catch (IOException e) {
			Log.w(TAG, "IOEx while checking MD5 of the update file", e);
		}*/
		
		return null;
	}

	private String readMD5(File updateFile) throws IOException {
		File md5File = new File(updateFile.getParentFile(), updateFile.getName() + ".md5");
		if(!md5File.exists()) return "";
		
		BufferedReader reader = new BufferedReader(new FileReader(md5File), 160);
		String md5;
		try {
			md5 = reader.readLine();
		} finally {
			reader.close();
		}
		
		return md5;
	}
	
	public void updateDownloadProgress(int downloaded, int total) {
		ProgressBar pb = mProgressBar;
		if(total < 0) {
			pb.setIndeterminate(true);
		} else {
			pb.setIndeterminate(false);
			pb.setMax(total);
		}
		pb.setProgress(downloaded);
		mDownloadedBytesTextView.setText((downloaded/(1024*1024)) + "/" + (total/(1024*1024)) + " MB");
	}
}
